/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2;
import lights.interfaces.*;

import java.util.*;

/**
 * An utility class for managing ops and groups of ops. 
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

class OpUtils 
{
 /**
  * Packs an array of ITuples into an array of arrays of ITuples, where each sub-array has size "size" - apart from 
  * the last one which contains the remaining elements. If "pack" is null or has zero length, this method returns an
  * array containg element null as sub-array.
  * 
  * @param pack tuples to pack
  * @param size of the sub-arrays
  * @return array of arrays of ITuples
  */  
 static Object[] packTuples(ITuple[] pack, int size)
 {
  if (pack == null || pack.length == 0) return new Object[] { null };  
 
  int div = pack.length / size;
  int mod = pack.length % size;
  int n = div + (mod == 0 ? 0 : 1); 
  Object[] ret = new Object[n];
 
  int index = 0;
  for (int i = 0; i < n; i++)
  {
   int aux = size;	
   if (i == n - 1 && mod != 0) aux = mod; 
   ITuple[] tmp = new ITuple[aux];
   for (int j = index; j < index + aux; j++) tmp[j - index] = pack[j];
   ret[i] = tmp;
   index += aux;
  }	
 
  return ret; 
 }

 /**
  * Packs an array of Reactions into an array of arrays of Reactions, where each sub-array has size "size" - apart 
  * from the last one which contains the remaining elements. 
  * 
  * @param pack reactions to pack
  * @param size of the sub-arrays
  * @return array of arrays of Reactions
  */  
 static Object[] packReactions(Reaction[] pack, int size)
 {
  int div = pack.length / size;
  int mod = pack.length % size;
  int n = div + (mod == 0 ? 0 : 1); 
  Object[] ret = new Object[n];
 
  int index = 0;
  for (int i = 0; i < n; i++)
  {
   int aux = size;	
   if (i == n - 1 && mod != 0) aux = mod; 
   Reaction[] tmp = new Reaction[aux];
   for (int j = index; j < index + aux; j++) tmp[j - index] = pack[j];
   ret[i] = tmp;
   index += aux;
  }	
 
  return ret; 
 }
 
 /**
  * Packs an array of ReactionIDs into an array of arrays of ReactionIDs, where each sub-array has size "size" - apart
  * from the last one which contains the remaining elements. 
  * 
  * @param pack reaction identifiers to pack
  * @param size of the sub-arrays
  * @return array of arrays of ReactionIDs
  */  
 static Object[] packReactionIDs(ReactionID[] pack, int size)
 {
  int div = pack.length / size;
  int mod = pack.length % size;
  int n = div + (mod == 0 ? 0 : 1); 
  Object[] ret = new Object[n];
 
  int index = 0;
  for (int i = 0; i < n; i++)
  {
   int aux = size;	
   if (i == n - 1 && mod != 0) aux = mod; 
   ReactionID[] tmp = new ReactionID[aux];
   for (int j = index; j < index + aux; j++) tmp[j - index] = pack[j];
   ret[i] = tmp;
   index += aux;
  }	
 
  return ret; 
 }
 
 /**
  * Packs the given tuples, reaction ids and reactions into an array of RemReconcileOps such that each of them carries
  * no more than "size" tuples / reaction ids / reactions. Sequence numbers and subtype codes (ACK, FIN for the last
  * RemReconcileOp) are automatically set.  
  * 
  * @param rco original RemReconcileOp (.. can happen that it is reused)
  * @param tup given tuples
  * @param ids given reaction ids
  * @param rea given reactions
  * @param size maximum number of items of the same kind to carry in each op
  * @return array of RemReconcileOps
  */
 static RemReconcileOp[] packRemReconcileOps(RemReconcileOp rco, Vector tup, Vector ids, Vector rea, int size)
 {
  Vector ret = new Vector();
  int k_tup = tup.size();
  int k_ids = ids.size();
  int k_rea = rea.size();
  int k = 0;
  
  if (k_tup == 0 && k_ids == 0 && k_rea == 0)
  {
   rco.tup = null;
   rco.ids = null;
   rco.rea = null;
   rco.subtype = LimeConstants.FIN;
   return new RemReconcileOp[] { rco };
  }	
  
  while (k_tup > 0 || k_ids > 0 || k_rea > 0)
  {
   RemReconcileOp tmp = new RemReconcileOp(LimeConstants.RECONCILE, LimeConstants.ACK, rco.name, rco.target, null);
   tmp.sourceT = rco.sourceT;
   tmp.targetT = rco.targetT;
   tmp.source = rco.source;
   tmp.target = rco.target;
   tmp.ti = rco.ti;
   tmp.seqnum = rco.seqnum + k;
   
   if (k_tup == 0) tmp.tup = null; 
   else 
   { 
   	int sub = (k_tup - size >= 0 ? size : k_tup);
   	ITuple[] aux = new ITuple[sub];
   	for (int i = 0; i < sub; i++) aux[i] = (ITuple) tup.remove(0); 
   	tmp.tup = aux;
   	k_tup -= sub;
   }
   
   if (k_ids == 0) tmp.ids = null; 
   else 
   { 
   	int sub = (k_ids - size >= 0 ? size : k_ids);
   	ReactionID[] aux = new ReactionID[sub];
   	for (int i = 0; i < sub; i++) aux[i] = (ReactionID) ids.remove(0); 
   	tmp.ids = aux;
   	k_ids -= sub;
   }
   
   if (k_rea == 0) tmp.rea = null; 
   else 
   { 
   	int sub = (k_rea - size >= 0 ? size : k_rea);
   	Reaction[] aux = new Reaction[sub];
   	for (int i = 0; i < sub; i++) aux[i] = (Reaction) rea.remove(0); 
   	tmp.rea = aux;
   	k_rea -= sub;
   }
   
   if (k_tup == 0 && k_ids == 0 && k_rea == 0) tmp.subtype = LimeConstants.FIN;
   ret.addElement(tmp);
   k++;
  }
  
  RemReconcileOp[] rcos = new RemReconcileOp[ret.size()];
  ret.copyInto(rcos);
  return rcos;
 }
 
 /**
  * Casts an array of RemoteOps into an array of RemTupleOps; of course each element of the original array must be
  * a RemTupleOp.
  * 
  * @param rtoa array of RemoteOps
  * @return array of RemTupleOps
  */ 
 static RemTupleOp[] toRemTupleOpArray(RemoteOp[] rtoa)
 {
  if (rtoa == null) return null;	
  RemTupleOp[] ret = new RemTupleOp[rtoa.length];	
  for (int i = 0; i < rtoa.length; i++) ret[i] = (RemTupleOp) rtoa[i];
  return ret;
 }

 /**
  * Separates the tuples of the given TupleOp into several TupleOps, according to their destination. The first element
  * of the returned array is the TupleOp containing tuples for this host - if there are tuples for this host.
  * 
  * @param to original TupleOp
  * @return array of TupleOps
  */
 static TupleOp[] separateT(TupleOp to)
 {
  Hashtable tmp = new Hashtable();
   	
  for (int i = 0; i < to.tuples.length; i++)
  {
   LimeServerID where = LimeWrapper.getDest(to.tuples[i]).getID();
   Vector v = (Vector) tmp.get(where);
   if (v == null)
   {
    v = new Vector();
    tmp.put(where, v);
   }   	
   v.addElement(to.tuples[i]);
  }	
 
  TupleOp[] ret = new TupleOp[tmp.size()]; // it is guaranteed that "size" is greater than zero
  
  Vector w = (Vector) tmp.remove(Location.HERE.getID());
  int j = 0;
 
  if (w != null) 
  {
   ITuple[] tup = new ITuple[w.size()]; // it is guaranteed that "size" is greater than zero
   w.copyInto(tup);
   ret[j] = new TupleOp(to.type, tup, to.result);	
   j++;
  }
  
  for (Enumeration e = tmp.keys(); e.hasMoreElements();) 
  {
   Vector aux = (Vector) tmp.get(e.nextElement());	
   ITuple[] tup = new ITuple[aux.size()]; // it is guaranteed that "size" is greater than zero
   aux.copyInto(tup);
   ret[j++] = new TupleOp(to.type, tup, to.result);
  }
  
  return ret;
 } 

 /**
  * Separates the reactions of the given ReactionOp into several ReactionOps, according to the current field within
  * their templates. Also return "scrambling" information.
  * <br>
  * The first element of the returned array is the ReactionOp containing reactions for this host - if any; the next 
  * one is the ReactionOp containing reactions for the whole federated tuplespace - if any.
  * 
  * @param ro original ReactionOp
  * @return instance of Scrambled
  */
 static Scrambled separateR(ReactionOp ro)
 {
  Hashtable tmp1 = new Hashtable();
  Hashtable tmp2 = new Hashtable();
   
  for (int i = 0; i < ro.rea.length; i++)
  {
   Location where = LimeWrapper.getCur(ro.rea[i].getTemplate());
   Vector v1 = (Vector) tmp1.get(where);
   Vector v2 = (Vector) tmp2.get(where);
   if (v1 == null)
   {
    v1 = new Vector();
    tmp1.put(where, v1);
    v2 = new Vector();
    tmp2.put(where, v2);
   }   	
   v1.addElement(ro.rea[i]);
   v2.addElement(new Integer(i));
  }	
 
  ReactionOp[] ret = new ReactionOp[tmp1.size()]; // it is guaranteed that "size" is greater than zero
  int[][] scrambled = new int[tmp2.size()][]; // it is guaranteed that "size" is greater than zero
  
  Vector w1 = (Vector) tmp1.remove(Location.HERE);
  Vector w2 = (Vector) tmp2.remove(Location.HERE);
  int j = 0;
 
  if (w1 != null) 
  {
   Reaction[] reas = new Reaction[w1.size()]; // it is guaranteed that "size" is greater than zero
   w1.copyInto(reas);

   scrambled[j] = new int[w2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < w2.size(); i++) scrambled[j][i] = ((Integer) w2.get(i)).intValue();
     
   ret[j] = new ReactionOp(ro.type, reas, ro.result);	
   j++;
  }
  
  w1 = (Vector) tmp1.remove(Location.UNSPECIFIED);
  w2 = (Vector) tmp2.remove(Location.UNSPECIFIED);
  
  if (w1 != null) 
  {
   Reaction[] reas = new Reaction[w1.size()]; // it is guaranteed that "size" is greater than zero
   w1.copyInto(reas);

   scrambled[j] = new int[w2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < w2.size(); i++) scrambled[j][i] = ((Integer) w2.get(i)).intValue();
      
   ret[j] = new ReactionOp(ro.type, reas, ro.result);	
   j++;
  }
  
  for (Enumeration e = tmp1.keys(); e.hasMoreElements();) 
  {
   Object key = e.nextElement();	
   Vector aux1 = (Vector) tmp1.get(key);	
   Vector aux2 = (Vector) tmp2.get(key);
   
   Reaction[] reas = new Reaction[aux1.size()]; // it is guaranteed that "size" is greater than zero
   aux1.copyInto(reas);
   scrambled[j] = new int[aux2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < aux2.size(); i++) scrambled[j][i] = ((Integer) aux2.get(i)).intValue();
     
   ret[j++] = new ReactionOp(ro.type, reas, ro.result);
  }
  
  return new Scrambled(ret, scrambled);
 } 
 
 /**
  * Separates the reaction identifiers of the given ReactionOp into several ReactionOps, according to their target 
  * location. Also return "scrambling" information.
  * <br>
  * The first element of the resulting array is the ReactionOp containing ReactionIDs that are "invalid" (= you can't
  * deregister them using "removeWeak" / on this tuplespace / on this host) - if any; the second element is the 
  * ReactionOp containing RactionIDs for this host - if any; the next one is the ReactionOp containing ReactionIDs for
  * the whole federated tuplespace - if any.
  * 
  * @param ro original ReactionOp
  * @return instance of Scrambled 
  */
 static Scrambled separateI(ReactionOp ro, String name)
 {
  Hashtable tmp1 = new Hashtable();
  Hashtable tmp2 = new Hashtable();
  
  for (int i = 0; i < ro.ids.length; i++)
  {
   Location where = ro.ids[i].getTarget();
   
   if (ro.ids[i].isPureStrong()                                       // strong reaction 
   	   || !ro.ids[i].getName().equals(name)                           // weak reaction for a different tuplespace
	   || !ro.ids[i].getCreationHost().equals(Location.HERE.getID())) // weak reaction was not previously installed on >>this<< host
   where = Location.INVALID;
   
   Vector v1 = (Vector) tmp1.get(where);
   Vector v2 = (Vector) tmp2.get(where);
   if (v1 == null)
   {
    v1 = new Vector();
    tmp1.put(where, v1);
    v2 = new Vector();
    tmp2.put(where, v2);
   }   	
   v1.addElement(ro.ids[i]);
   v2.addElement(new Integer(i));
  }	
 
  ReactionOp[] ret = new ReactionOp[tmp1.size()]; // it is guaranteed that "size" is greater than zero
  int[][] scrambled = new int[tmp2.size()][]; // it is guaranteed that "size" is greater than zero  
  
  Vector w1 = (Vector) tmp1.remove(Location.INVALID);
  Vector w2 = (Vector) tmp2.remove(Location.INVALID);
  int j = 0;
 
  if (w1 != null) 
  {
   ReactionID[] idss = new ReactionID[w1.size()]; // it is guaranteed that "size" is greater than zero
   w1.copyInto(idss);
   
   scrambled[j] = new int[w2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < w2.size(); i++) scrambled[j][i] = ((Integer) w2.get(i)).intValue();
   
   ret[j] = new ReactionOp(ro.type, idss, ro.result);	
   j++;
  }
  
  w1 = (Vector) tmp1.remove(Location.HERE);
  w2 = (Vector) tmp2.remove(Location.HERE);
  
  if (w1 != null) 
  {
   ReactionID[] idss = new ReactionID[w1.size()]; // it is guaranteed that "size" is greater than zero
   w1.copyInto(idss);

   scrambled[j] = new int[w2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < w2.size(); i++) scrambled[j][i] = ((Integer) w2.get(i)).intValue();
      
   ret[j] = new ReactionOp(ro.type, idss, ro.result);	
   j++;
  }
  
  w1 = (Vector) tmp1.remove(Location.UNSPECIFIED);
  w2 = (Vector) tmp2.remove(Location.UNSPECIFIED);
  
  if (w1 != null) 
  {
   ReactionID[] idss = new ReactionID[w1.size()]; // it is guaranteed that "size" is greater than zero
   w1.copyInto(idss);

   scrambled[j] = new int[w2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < w2.size(); i++) scrambled[j][i] = ((Integer) w2.get(i)).intValue();
      
   ret[j] = new ReactionOp(ro.type, idss, ro.result);	
   j++;
  }
  
  for (Enumeration e = tmp1.keys(); e.hasMoreElements();) 
  {
   Object key = e.nextElement();	
   Vector aux1 = (Vector) tmp1.get(key);
   Vector aux2 = (Vector) tmp2.get(key);
   
   ReactionID[] idss = new ReactionID[aux1.size()]; // it is guaranteed that "size" is greater than zero
   aux1.copyInto(idss);
   
   scrambled[j] = new int[aux2.size()]; // it is guaranteed that "size" is greater than zero
   for (int i = 0; i < aux2.size(); i++) scrambled[j][i] = ((Integer) aux2.get(i)).intValue();
     
   ret[j++] = new ReactionOp(ro.type, idss, ro.result);
  }
  
  return new Scrambled(ret, scrambled);
 } 
 
} 

/**
 * Auxiliary class for returning "two data".
 */

class Scrambled
{
 // private data members	
 private ReactionOp[] ros;	
 private int[][] scrambled;
 
 /**
  * Constructor method.
  * 
  * @param ros array of ReactionOps
  * @param scrambled ..records "scrambling" order
  */
 Scrambled(ReactionOp[] ros, int[][] scrambled)
 {
  this.ros = ros;
  this.scrambled = scrambled;  
 }
 
 /**
  * Returns stored array of ReactionOps.
  * 
  * @return array of ReactionOps
  */
 ReactionOp[] getReactionOp()
 {
  return ros;		
 }			
							
 /**
  * Returns scrambling order.
  * 
  * @return scrambling order
  */
 int[][] getScrambled()
 {	
  return scrambled;	
 }
 
} 
